说明
本文发布较早,查看 GitHub 项目了解最新动态。(2023 年 7 月 注)
准备
IDE:Visual Studio
Language:VB.NET / C#
GitHub:AutoPaint.NET
通过编程实现自动画画,以后玩你画我猜再也不用担心被吐槽啦ヾ(゚∀゚ゞ)
第一节 导入图片
程序画画,首先得导入一张图片作为模板。
图像格式
我们选择使用位图,它是由像素数据定义的图像格式
WinForm 下使用封装了 GDI+ 位图的 System.Drawing.Bitmap 对象
UWP 下可以使用 CanvasBitmap 对象(需要安装 Win2D 的 Nuget 包)
导入本地文件
直接导入本地图像资源
详情见 System.Drawing.Bitmap 构造函数
使用屏幕截图
先用搜索引擎搜图,然后直接截屏
System.Drawing.Graphics 对象提供从屏幕到 Graphics 的位块传输
动态合成图像
比如合成文本或者图案到指定的图像上
System.Drawing.Graphics 对象提供一系列的 GDI+ 绘图命令
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''
''' 返回指定矩形区域的屏幕图像
'''
''' 指定的矩形区域
'''
Public Function GetScreenImage(ByVal rect As Rectangle) As Bitmap
Dim resultBmp As New Bitmap(rect.Width, rect.Height)
Using pg As Graphics = Graphics.FromImage(resultBmp)
pg.CopyFromScreen(rect.X, rect.Y, 0, 0, New Size(rect.Width, rect.Height))
End Using
Return resultBmp
End Function
VB.NET-GetScreenImage
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''
''' 返回指定文字生成的位图
'''
''' 文本
''' 字体
''' 位图宽度
''' 位图高度
'''
Public Function GetTextImage(ByVal text As String, ByVal font As Font, ByVal width As Integer, ByVal height As Integer) As Bitmap
Dim resultBmp As New Bitmap(width, height)
Using pg = Graphics.FromImage(resultBmp)
pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias '抗锯齿
pg.DrawString(text, font, Brushes.Black, 0, 0)
End Using
Return resultBmp
End Function
VB.NET-GetTextImage
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 返回指定矩形区域的屏幕图像
///
/// 指定的矩形区域
///
public Bitmap GetScreenImage(Rectangle rect)
{
Bitmap resultBmp = new Bitmap(rect.Width, rect.Height);
using (Graphics pg = Graphics.FromImage(resultBmp)) {
pg.CopyFromScreen(rect.X, rect.Y, 0, 0, new Size(rect.Width, rect.Height));
}
return resultBmp;
}
C#-GetScreenImage
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
///
/// 返回指定文字生成的位图
///
/// 文本
/// 字体
/// 位图宽度
/// 位图高度
///
public Bitmap GetTextImage(string text, Font font, int width, int height)
{
Bitmap resultBmp = new Bitmap(width, height);
using (pg == Graphics.FromImage(resultBmp)) {
pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias;
//抗锯齿
pg.DrawString(text, font, Brushes.Black, 0, 0);
}
return resultBmp;
}
C#-GetTextImage
第二节 图像处理
接下来对模板图像进行处理,以便于下一步的轨迹寻找。
二值化
基于一个阈值 T,大于 T 的像素群设定为白色,小于 T 的像素群设定为黑色
也就是将整个图像呈现出明显的只有黑和白的视觉效果
![](//images0.cnblogs.com/blog2015/726127/201505/111259020171292.png)
图2-1 全局二值化
细化
将粗线条细化为细线条(线条宽度通常为一像素)
细化算法是为了找出图像的轮廓,对不同图像的效果不同
![](//images0.cnblogs.com/blog2015/726127/201505/111315547351668.png)
图2-2 轮廓化
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''
''' 提供对位图图像和颜色的一系列操作的对象
'''
Public Class ImageProcess
'''
''' 基于RGB根据指定阈值判断两个颜色是否相近
'''
Public Function CompareRGB(ByVal Color1 As Color, ByVal Color2 As Color, ByVal Distance As Single) As Boolean
Dim r As Integer = Int(Color1.R) - Int(Color2.R)
Dim g As Integer = Int(Color1.G) - Int(Color2.G)
Dim b As Integer = Int(Color1.B) - Int(Color2.B)
Dim absDis As Integer = Math.Sqrt(r * r + g * g + b * b)
If absDis < Distance Then
Return True
Else
Return False
End If
End Function
'''
''' 基于HSB根据指定阈值判断两个颜色是否相近
'''
Public Function CompareHSB(ByVal Color1 As Color, ByVal Color2 As Color, ByVal Distance As Single) As Boolean
'向量距离
'Dim h As Single = (Color1.GetHue - Color2.GetHue) / 360
'Dim s As Single = Color1.GetSaturation - Color2.GetSaturation
'Dim b As Single = Color1.GetBrightness - Color2.GetBrightness
'Dim absDis As Single = Math.Sqrt(h * h + s * s + b * b)
'If absDis < Distance Then
' Return True
'Else
' Return False
'End If
'向量夹角
Dim h1 As Single = Color1.GetHue / 360
Dim s1 As Single = Color1.GetSaturation
Dim b1 As Single = Color1.GetBrightness
Dim h2 As Single = Color2.GetHue / 360
Dim s2 As Single = Color2.GetSaturation
Dim b2 As Single = Color2.GetBrightness
Dim absDis As Single = (h1 * h2 + s1 * s2 + b1 * b2) / (Math.Sqrt(h1 * h1 + s1 * s1 + b1 * b1) * Math.Sqrt(h2 * h2 + s2 * s2 + b2 * b2))
If absDis > Distance / 5 + 0.8 Then
Return True
Else
Return False
End If
End Function
'''
''' 返回指定颜色的中值
'''
Public Function gethHD(ByVal color1 As Color)
Dim HD, r, g, b As Integer
r = color1.R
g = color1.G
b = color1.B
HD = (r + g + b) / 3
Return HD
End Function
'''
''' 返回指定位图的颜色数组
'''
'''
'''
Public Function GetColorArr(ByRef gBitmap As Bitmap) As Color(,)
Dim TempArr(gBitmap.Width - 1, gBitmap.Height - 1) As Color
For i = 0 To gBitmap.Width - 1
For j = 0 To gBitmap.Height - 1
TempArr(i, j) = gBitmap.GetPixel(i, j)
Next
Next
Return TempArr
End Function
'''
''' 返回指定矩形区域的屏幕图像
'''
''' 指定的矩形区域
'''
Public Function GetScreenImage(ByVal rect As Rectangle) As Bitmap
Dim resultBmp As New Bitmap(rect.Width, rect.Height)
Using pg As Graphics = Graphics.FromImage(resultBmp)
pg.CopyFromScreen(rect.X, rect.Y, 0, 0, New Size(rect.Width, rect.Height))
End Using
Return resultBmp
End Function
'''
''' 返回指定文字生成的位图
'''
''' 文本
''' 字体
''' 位图宽度
''' 位图高度
'''
Public Function GetTextImage(ByVal text As String, ByVal font As Font, ByVal width As Integer, ByVal height As Integer) As Bitmap
Dim resultBmp As New Bitmap(width, height)
Using pg = Graphics.FromImage(resultBmp)
pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias '抗锯齿
pg.DrawString(text, font, Brushes.Black, 0, 0)
End Using
Return resultBmp
End Function
'''
''' 返回指定图位图的二值化图像
'''
'''
'''
'''
Public Function GetThresholdImage(ByVal gBitmap As Bitmap, ByVal gSplitNum As Single, Optional IsHSB As Boolean = False) As Bitmap
Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height)
Dim ColorArr(,) = GetColorArr(gBitmap)
Dim TempHD As Integer
Dim IsOverThreshold = Function(ByVal C1 As Color, ByVal gNum As Single)
TempHD = gethHD(C1)
Return (If(IsHSB, (C1.GetHue / 360 + C1.GetSaturation + C1.GetBrightness) / 3 0,
gethHD(Color1) - gethHD(Color2) > 0)
End Function
Dim ColorArr(,) = GetColorArr(gBitmap)
For i = 1 To gBitmap.Width - 2
For j = 1 To gBitmap.Height - 2
ResultBitmap.SetPixel(i, j, Color.White)
Color1 = ColorArr(i, j)
For p = 0 To 3
Color2 = ColorArr(i + xArray2(p), j + yArray2(p))
If Not CompareColor(Color1, Color2, gDistance) And CompareColorExtra(Color1, Color2) Then
ResultBitmap.SetPixel(i, j, Color.Black)
' ResultBitmap.SetPixel(i, j, ColorArr(i, j))
End If
Next
Next
Next
Return ResultBitmap
End Function
'''
''' 返回指定位图的空心图像
'''
'''
'''
Public Function GetAroundImage(gBitmap As Bitmap)
Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height)
Dim ImageBolArr(,) As Integer = GetImageBol(gBitmap)
For i = 0 To gBitmap.Width - 1
For j = 0 To gBitmap.Height - 1
If ImageBolArr(i, j) = 1 AndAlso CheckPointAround(ImageBolArr, i, j) = False Then
ResultBitmap.SetPixel(i, j, Color.Black)
Else
ResultBitmap.SetPixel(i, j, Color.White)
End If
Next
Next
Return ResultBitmap
End Function
'''
''' 返回指定位图的反相图像
'''
'''
'''
Public Function GetInvertImage(gBitmap As Bitmap)
Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height)
Dim ImageBolArr(,) As Integer = GetImageBol(gBitmap)
For i = 0 To gBitmap.Width - 1
For j = 0 To gBitmap.Height - 1
If ImageBolArr(i, j) = 1 Then
ResultBitmap.SetPixel(i, j, Color.White)
Else
ResultBitmap.SetPixel(i, j, Color.Black)
End If
Next
Next
Return ResultBitmap
End Function
'''
''' 返回指定位图的色块图像
'''
'''
'''
Public Function GetLumpImage(gBitmap As Bitmap, Optional Range As Integer = 10)
Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height)
Dim ColorArr(,) = GetColorArr(gBitmap)
Dim R, G, B As Integer
For i = 0 To gBitmap.Width - 1
For j = 0 To gBitmap.Height - 1
R = Int(ColorArr(i, j).R / Range) * Range
G = Int(ColorArr(i, j).G / Range) * Range
B = Int(ColorArr(i, j).B / Range) * Range
ResultBitmap.SetPixel(i, j, Color.FromArgb(R, G, B))
Next
Next
Return ResultBitmap
End Function
'''
''' 返回指定位图的二值化数据
'''
'''
'''
Private Function GetImageBol(ByVal gBitmap As Bitmap) As Integer(,)
Dim ResultArr(gBitmap.Width - 1, gBitmap.Height - 1) As Integer
For i = 0 To gBitmap.Width - 1
For j = 0 To gBitmap.Height - 1
If Not gBitmap.GetPixel(i, j).Equals(Color.FromArgb(255, 255, 255)) Then
ResultArr(i, j) = 1
Else
ResultArr(i, j) = 0
End If
Next
Next
Return ResultArr
End Function
'''
''' 检查一个点是否被包围
'''
'''
'''
'''
'''
Private Function CheckPointAround(BolArr As Integer(,), ByVal x As Integer, ByVal y As Integer) As Boolean
If Not (x > 0 And y > 0 And x < BolArr.GetUpperBound(0) And y < BolArr.GetUpperBound(1)) Then Return True
If BolArr(x - 1, y) = 1 And BolArr(x + 1, y) = 1 And BolArr(x, y - 1) = 1 And BolArr(x, y + 1) = 1 Then
Return True '当前点为实体内部
Else
Return False '当前点为实体边缘
End If
End Function
End Class
VB.NET-ImageProcess
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
///
/// 提供对位图图像和颜色的一系列操作的对象
///
public class ImageProcess
{
///
/// 基于RGB根据指定阈值判断两个颜色是否相近
///
public bool CompareRGB(Color Color1, Color Color2, float Distance)
{
int r = Conversion.Int(Color1.R) - Conversion.Int(Color2.R);
int g = Conversion.Int(Color1.G) - Conversion.Int(Color2.G);
int b = Conversion.Int(Color1.B) - Conversion.Int(Color2.B);
int absDis = Math.Sqrt(r * r + g * g + b * b);
if (absDis Distance / 5 + 0.8) {
return true;
} else {
return false;
}
}
///
/// 返回指定颜色的中值
///
public object gethHD(Color color1)
{
int HD = 0;
int r = 0;
int g = 0;
int b = 0;
r = color1.R;
g = color1.G;
b = color1.B;
HD = (r + g + b) / 3;
return HD;
}
///
/// 返回指定位图的颜色数组
///
///
///
public Color[,] GetColorArr(ref Bitmap gBitmap)
{
Color[,] TempArr = new Color[gBitmap.Width, gBitmap.Height];
for (i = 0; i yCount, xCount, yCount)
For Theat = 0 To Math.PI * 2 Step 1 / R
Dim dx As Integer = CP.X + R * Math.Cos(Theat)
Dim dy As Integer = CP.Y + R * Math.Sin(Theat)
If Not (dx > 0 And dy > 0 And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = 1 Then
BolArr(dx, dy) = 0
Me.CreateNewSequence()
Me.AddPoint(New PointF(dx, dy))
CheckMove(BolArr, dx, dy, 0)
NewStart = True
End If
Next
Next
End Sub
'''
''' 返回指定像素位置的权值
'''
Private Function GetAroundValue(ByRef BolArr(,) As Integer, ByVal x As Integer, ByVal y As Integer) As Integer
Dim dx, dy, ResultValue As Integer
Dim xBound As Integer = BolArr.GetUpperBound(0)
Dim yBound As Integer = BolArr.GetUpperBound(1)
For i = 0 To 7
dx = x + xArray(i)
dy = y + yArray(i)
If dx > 0 And dy > 0 And dx < xBound And dy < yBound Then
If BolArr(dx, dy) = 1 Then
ResultValue += 1
End If
End If
Next
Return ResultValue
End Function
End Class
VB.NET-SequenceManager
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''
''' 表示一条画图曲线的绘制序列
'''
Public Class PointSequence
Public PointList As New List(Of PointF)
End Class
VB.NET-PointSequence
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
///
/// 提供由图像循迹生成绘图序列的对象
///
public class SequenceManager
{
///
/// 绘制序列的集合
///
public List SequenceList;
public SequenceManager(int[,] BolArr)
{
SequenceList = new List();
CalculateSequence(BolArr);
}
///
/// 创建新的序列
///
private void CreateNewSequence()
{
SequenceList.Add(new PointSequence());
}
///
/// 添加新的位置
///
private void AddPoint(PointF point)
{
SequenceList.Last.PointList.Add(point);
}
int[] xArray = {
-1,
0,
1,
1,
1,
0,
-1,
-1
};
int[] yArray = {
-1,
-1,
-1,
0,
1,
1,
1,
0
};
bool NewStart;
///
/// 递归循迹
///
private void CheckMove(ref int[,] BolArr, int x, int y, int StepNum)
{
Application.DoEvents();
//处理主线程消息
if (StepNum > 10000)
return;
int xBound = BolArr.GetUpperBound(0);
int yBound = BolArr.GetUpperBound(1);
int dx = 0;
int dy = 0;
int AroundValue = GetAroundValue(ref BolArr, x, y);
if (AroundValue > 2 && AroundValue < 8) {
return;
}
for (i = 0; i 0 & dy > 0 & dx < xBound & dy 0 & dx < xCount & dy 0 & dx < xBound & dy |